#include "chatservice.hpp"
#include "PCHAT.pb.h"
#include "offlinemessagemodel.hpp"

#include <muduo/base/Logging.h>
#include <vector>

ChatService& ChatService::getInstance() {
	static ChatService service;
	return service;
}

//构造函数：注册消息以及对应的回调操作
//把网络模块和业务模块解耦的核心
ChatService::ChatService() {
	//用户基本业务管理相关事件处理回调函数注册
	m_msgHandlerMap.insert({ LOGIN_MSG, std::bind(&ChatService::login, this, _1, _2, _3) });
	m_msgHandlerMap.insert({ REG_MSG, std::bind(&ChatService::reg, this, _1, _2, _3) });
	m_msgHandlerMap.insert({ ONE_CHAT_MSG, std::bind(&ChatService::oneChat, this, _1, _2, _3) });
	m_msgHandlerMap.insert({ ADD_FRIEND_MSG, std::bind(&ChatService::addFriend, this, _1, _2, _3) });
	m_msgHandlerMap.insert({ CREATE_GROUP_MSG, std::bind(&ChatService::createGroup, this, _1, _2, _3) });
	m_msgHandlerMap.insert({ LOGINOUT_MSG, std::bind(&ChatService::loginOut, this, _1, _2, _3) });

	//群组业务管理相关事件处理回调函数注册
	m_msgHandlerMap.insert({ ADD_GROUP_MSG, std::bind(&ChatService::addGroup, this, _1, _2, _3) });
	m_msgHandlerMap.insert({ GROUP_CHAT_MSG, std::bind(&ChatService::groupChat, this, _1, _2, _3) });

	if (m_redis.connect()) {
		//设置上报消息的回调
		m_redis.init_notify_handler(std::bind(&ChatService::handleRedisSubscribeMessage, this, _1, _2));
	}
}

void ChatService::setUserInfo(LoginResponse* login_response, P_User* user_info, User& user)
{
	int user_id = user.getId();
	user_info->set_user_id(user_id);
	user_info->set_user_name(user.getName());

	auto offline_msg_vec = m_offlineMessageModel.query(user_id);
	m_offlineMessageModel.remove(user_id);
	auto friend_vec = m_friendModel.query(user_id);
	auto groupuser_vec = m_groupModel.queryGroups(user_id);

	for (auto& msg : offline_msg_vec) {
		OfflineMsg* offlineMsg = login_response->add_offline_msgs();
		offlineMsg->ParseFromString(msg);
	}

	if (!friend_vec.empty()) {
		login_response->mutable_friends()->Reserve(friend_vec.size());
		for (auto& user : friend_vec) {
			//add_friend()方法会在friends数组中追加一个元素，这个元素的地址就是pb_friend
			auto* pb_friend = login_response->add_friends();
			pb_friend->set_user_id(user.getId());
			pb_friend->set_user_name(user.getName());
			pb_friend->set_state(user.getState());
		}
	}

	if (!groupuser_vec.empty()) {
		//group:[{groupid:[xxx,xxx,xxx,xxx]}]
		login_response->mutable_groups()->Reserve(groupuser_vec.size());
		for (auto& group : groupuser_vec) {
			auto* pb_group = login_response->add_groups();
			pb_group->set_group_id(group.getId());
			pb_group->set_group_name(group.getName());
			pb_group->set_description(group.getDesc());

			for (auto& user : group.getUsers()) {
				auto* pb_gropumember = pb_group->add_members();
				auto* pb_user_info = pb_gropumember->mutable_user();
				pb_user_info->set_user_id(user.getId());
				pb_user_info->set_user_name(user.getName());
				pb_user_info->set_state(user.getState());
				pb_gropumember->set_role(user.getRole());
			}
		}
	}
}

void ChatService::setUserStateOnline(User& user)
{
	user.setState("online");
	m_userModel.updateState(user);
}

void ChatService::setUserStateOffline(User& user)
{
	user.setState("offline");
	m_userModel.updateState(user);
}

bool ChatService::loginOut(const TcpConnectionPtr& conn, const ChatMessage& chatMessage, const Timestamp& time) {
	const LogoutRequest& loginoutRequest = chatMessage.logout_req();
	int userid = loginoutRequest.user_id();
	{
		lock_guard<mutex> lock(m_connMutex);
		auto it = m_userConnMap.find(userid);
		if (it != m_userConnMap.end()) {
			m_userConnMap.erase(it);
		}
	}

	//用户注销，相当于就是下线，在redis中取消订阅通道
	m_redis.unsubscribe(userid);

	//修改用户的状态信息为offline
	User user(userid);
	setUserStateOffline(user);
	return true;
}

bool ChatService::login(const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage, const Timestamp& time)
{
	auto& loginreq = chatMessage.login_req();
	auto id = loginreq.user_id();
	auto& pwd = loginreq.password();

	ChatMessage response;
	response.set_msg_type(MessageType::LOGIN_MSG_ACK);
	auto* loginresponse = response.mutable_login_resp();

	auto user = m_userModel.query(id);
	if (user.getId() == id && user.getPassword() == pwd)
	{
		if (user.getState() == "online")
		{
			loginresponse->set_errnum(2);
			loginresponse->set_errmsg("this user already login! please using another account!");
		}
		else
		{
			{
				lock_guard<mutex> lock(m_connMutex);
				m_userConnMap.insert({ id, conn });
			}

			//id用户登录成功以后，向redis订阅channel(id)
			m_redis.subscribe(id);

			setUserStateOnline(user);

			loginresponse->set_errnum(0);
			auto* user_info = loginresponse->mutable_user_info();
			setUserInfo(loginresponse, user_info, user);
		}
	}
	else {
		loginresponse->set_errnum(1);
		loginresponse->set_errmsg("id or password is invalid!");
	}
	conn->send(response.SerializeAsString());
	return true;
}

// 处理注册业务：用户填一个name password字段就可以注册了
bool ChatService::reg(const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage, const Timestamp& time)
{
	auto& registerRequest = chatMessage.register_req();
	auto& name = registerRequest.user_name();
	auto& pwd = registerRequest.password();
	User user;
	user.setName(name);
	user.setPassword(pwd);
	auto state = m_userModel.insert(user);

	ChatMessage response;
	response.set_msg_type(MessageType::REG_MSG_ACK);
	auto* register_response = response.mutable_register_resp();
	register_response->set_user_id(user.getId());
	register_response->set_errnum(state ? 0 : 1);
	conn->send(response.SerializeAsString());
	return true;
}

MsgHandler ChatService::getHandler(int msgid) {
	if (m_msgHandlerMap.find(msgid) == m_msgHandlerMap.end()) {
		//返回一个默认的事件处理器,空操作
		return [msgid](const TcpConnectionPtr& conn, const ChatMessage& msg, const Timestamp& time) {
			LOG_ERROR << "msgid:" << msgid << "can not find Handler!";
			return true;
			};
	}
	return m_msgHandlerMap[msgid];
}

void ChatService::clientCloseException(const TcpConnectionPtr& conn) {
	//找到conn连接对应的用户id,将用户的状态修改为offline
	User user(-1);
	{
		lock_guard<mutex> lock(m_connMutex);
		for (auto it = m_userConnMap.begin(); it != m_userConnMap.end(); ++it) {
			if (it->second == conn) {
				//从map表删除用户的连接信息
				user.setId(it->first);
				m_userConnMap.erase(it);
				break;
			}
		}
	}

	// 更新用户的状态信息(对于user没找着的这种问题，可以判断一下user.getId()是否为-1)
	//不判断也没问题，因为当user没找着时，调用updateState()会因为找不对id为-1的记录而更新失败
	if (user.getId() != -1)
	{
		//用户注销，相当于就是下线，在redis中取消订阅通道
		m_redis.unsubscribe(user.getId());
		setUserStateOffline(user);
	}
}

bool ChatService::oneChat(const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage,
	const Timestamp& time)
{
	int toid = chatMessage.one_chat_msg().to_user_id();
	{
		lock_guard<mutex> lock(m_connMutex);
		auto it = m_userConnMap.find(toid);
		if (it != m_userConnMap.end()) 
		{
			// 用户toid在线(在同一台服务器上)，转发消息(将用户a发送给服务器的消息原封不动地发送给用户toid即可)
			it->second->send(chatMessage.SerializeAsString());
			return true; //出锁的作用域了
		}
	}

	//查询toid是否在线(在其他服务器上)
	User user = m_userModel.query(toid);
	if (user.getState() == "online") 
	{
		return m_redis.publish(toid, chatMessage.SerializeAsString());
	}

	// 用户toid不在线,存储离线消息
	OfflineMsg offlineMsg;
	offlineMsg.set_msgid(MsgChatType::PRIVATE_CHAT);
	offlineMsg.mutable_one_chat_msg()->CopyFrom(chatMessage.one_chat_msg());
	m_offlineMessageModel.insert(toid, offlineMsg.SerializeAsString());
	return true;
}

//服务器异常后，业务重置方法
void ChatService::reset() {
	//把所有online状态的用户设置成offline
	m_userModel.resetState();
	//其他重置业务加在这里...
}

//添加好友业务
bool ChatService::addFriend(
	const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage,
	const Timestamp& time) 
{
	const AddFriendRequest& add_friend_req = chatMessage.add_friend_req();
	int userid = add_friend_req.user_id();
	int friendid = add_friend_req.friend_id();

	return m_friendModel.insert(userid, friendid);
}

//创建群组业务
bool ChatService::createGroup(
	const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage,
	const Timestamp& time) 
{
	auto& createGroupRequest = chatMessage.create_group_req();
	int creator_id = createGroupRequest.creator_id();
	string group_name = createGroupRequest.group_name();
	string group_desc = createGroupRequest.description();

	//存储新创建的群组信息，操作allgruop表
	Group group(-1, group_name, group_desc);
	if (m_groupModel.createGroup(group)) {
		// GroupUser表中存储群组创建人信息，操作groupuser表
		return m_groupModel.addGroup(creator_id, group.getId(), "creator");
	}
	return false;
}

//普通用户加入群组
bool ChatService::addGroup(
	const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage,
	const Timestamp& time)
{
	auto userid = chatMessage.add_group_req().user_id();
	auto groupid = chatMessage.add_group_req().group_id();
	m_groupModel.addGroup(userid, groupid, "normal");
	return true;
}

//群组聊天业务
bool ChatService::groupChat(
	const TcpConnectionPtr& conn,
	const ChatMessage& chatMessage,
	const Timestamp& time) 
{
	auto& groupChatMessage = chatMessage.group_chat_msg();
	int userid = groupChatMessage.chat_content().from_user_id();
	int groupid = groupChatMessage.group_id();
	auto useridVec = m_groupModel.queryGroupUsers(userid, groupid);

	//最好写在for循环外边，保证群发的在线用户都能收到消息，即群内所有在线用户都受到消息才允许有群成员下线(保证线程安全，因为我们设置的线程数为4，所以有3个work线程都会调用onMessage方法(在任何时候)
	lock_guard<mutex> lock(m_connMutex); 	
	for (auto id : useridVec) {
		auto it = m_userConnMap.find(id);
		auto msg = chatMessage.SerializeAsString();
		if (it != m_userConnMap.end()) {
			it->second->send(msg);
		}
		else {
			User user = m_userModel.query(id);
			if (user.getState() == "online") {
				m_redis.publish(id, msg);
			}
			else {
				OfflineMsg offlineMsg;
				offlineMsg.set_msgid(MsgChatType::GROUP_CHAT);
				GroupChatMessage* groupChatMeg = offlineMsg.mutable_group_chat_msg();
				groupChatMeg->CopyFrom(groupChatMessage);
				m_offlineMessageModel.insert(id, offlineMsg.SerializeAsString());
			}
		}
	}
	return true;
}

//从redis消息队列中获取订阅的消息(从通道里边拿到消息，发送给对应的客户端)
void ChatService::handleRedisSubscribeMessage(int userid, string msg) {
	lock_guard<mutex> lock(m_connMutex);
	auto it = m_userConnMap.find(userid);
	if (it != m_userConnMap.end()) {
		it->second->send(msg);
		return;
	}
	//存储该用户的离线消息
	//这里为什么还要存储离线消息呢？因为假设用户A(server1上)在数据库中查到用户B(server2上)是在线的，然后通过redis向用户B发送消息，但是用户B可能在这个过程中下线了，所以这里需要存储离线消息
	m_offlineMessageModel.insert(userid, msg);
}